package com.hisense.davidmoten.rtree.geometry;

import com.github.davidmoten.guavamini.annotations.VisibleForTesting;
import com.hisense.davidmoten.rtree.geometry.internal.*;
import com.vividsolutions.jts.geom.LineString;

/**
 * 几何工厂工具类。
 * 提供创建点、矩形、圆和线段的静态方法。
 * 支持地理坐标场景：经度归一化到 [-180, 180) 以及跨反经线矩形处理。
 * 所有返回对象为不可变几何实现，适配用于 RTree 空间索引。
 */
public final class Geometries {

    /**
     * 工具类构造方法私有化，禁止实例化。
     */
    private Geometries() {
        // prevent instantiation
    }

    /**
     * 创建一个双精度点。
     * @param x X 坐标
     * @param y Y 坐标
     * @return 点对象
     */
    public static Point point(double x, double y) {
        return PointDouble.create(x, y);
    }

    /**
     * 创建一个单精度点。
     * @param x X 坐标
     * @param y Y 坐标
     * @return 点对象
     */
    public static Point point(float x, float y) {
        return PointFloat.create(x, y);
    }

    /**
     * 使用经纬度创建点（双精度）。
     * 经度会先归一化到区间 [-180, 180) 以避免越界。
     * @param lon 经度（可超过 180/-180）
     * @param lat 纬度（建议范围 [-90, 90]）
     * @return 地理点对象
     */
    public static Point pointGeographic(double lon, double lat) {
        return point(normalizeLongitudeDouble(lon), lat);
    }

    /**
     * 使用经纬度创建点（单精度）。
     * 经度会先归一化到区间 [-180, 180) 以避免越界。
     * @param lon 经度（可超过 180/-180）
     * @param lat 纬度（建议范围 [-90, 90]）
     * @return 地理点对象
     */
    public static Point pointGeographic(float lon, float lat) {
        return point(normalizeLongitude(lon), lat);
    }

    /**
     * 创建一个双精度矩形。
     * 不对输入坐标做经度归一化处理。
     * @param x1 左下角 X
     * @param y1 左下角 Y
     * @param x2 右上角 X
     * @param y2 右上角 Y
     * @return 矩形对象
     */
    public static Rectangle rectangle(double x1, double y1, double x2, double y2) {
        return rectangleDouble(x1, y1, x2, y2);
    }

    /**
     * 创建一个单精度矩形。
     * 不对输入坐标做经度归一化处理。
     * @param x1 左下角 X
     * @param y1 左下角 Y
     * @param x2 右上角 X
     * @param y2 右上角 Y
     * @return 矩形对象
     */
    public static Rectangle rectangle(float x1, float y1, float x2, float y2) {
        return RectangleFloat.create(x1, y1, x2, y2);
    }

    /**
     * 使用经纬度创建地理矩形（double 输入）。
     * 内部委托给 float 版本以保持一致的数值行为。
     * @param lon1 左下角经度
     * @param lat1 左下角纬度
     * @param lon2 右上角经度
     * @param lat2 右上角纬度
     * @return 地理矩形对象
     */
    public static Rectangle rectangleGeographic(double lon1, double lat1, double lon2,
            double lat2) {
        return rectangleGeographic((float) lon1, (float) lat1, (float) lon2, (float) lat2);
    }

    /**
     * 使用经纬度创建地理矩形（单精度）。
     * 处理逻辑：
     * 1) 对两端经度进行归一化到 [-180, 180)；
     * 2) 若归一化后 x2 < x1，说明跨越了 -180/180 经线，令 x2 += 360；
     * 返回的矩形在经度维度可能落在 [x1, x2]，长度不超过 360。
     * 注意：纬度未做裁剪，假定输入在合法范围 [-90, 90]。
     * @param lon1 左下角经度
     * @param lat1 左下角纬度
     * @param lon2 右上角经度
     * @param lat2 右上角纬度
     * @return 地理矩形对象
     */
    public static Rectangle rectangleGeographic(float lon1, float lat1, float lon2, float lat2) {
        // 对输入经度进行归一化，使其落在 [-180, 180)
        float x1 = normalizeLongitude(lon1);
        float x2 = normalizeLongitude(lon2);
        // 若第二个经度比第一个小，说明跨越了 -180/180 经线，向右展开
        if (x2 < x1) {
            x2 += 360;
        }
        // 使用归一化后的经度构造矩形
        return rectangle(x1, lat1, x2, lat2);
    }

    /**
     * 内部帮助方法：创建一个双精度矩形。
     * @param x1 左下角 X
     * @param y1 左下角 Y
     * @param x2 右上角 X
     * @param y2 右上角 Y
     * @return 矩形对象
     */
    private static Rectangle rectangleDouble(double x1, double y1, double x2, double y2) {
        return  RectangleDouble.create(x1, y1, x2, y2);
    }

    public static Circle circle(double x, double y, double radius) {
        return CircleDouble.create(x, y, radius);
    }

    public static Circle circle(float x, float y, float radius) {
        return CircleFloat.create(x, y, radius);
    }

    public static Line line(double x1, double y1, double x2, double y2) {
        return LineDouble.create(x1, y1, x2, y2);
    }

    public static Line line(float x1, float y1, float x2, float y2) {
        return LineFloat.create(x1, y1, x2, y2);
    }
    public static Line line(LineString lineString){
        return PolylineFloat.create(lineString);
    }

    /**
     * 供测试使用的 double 版本经度归一化入口。
     * 行为与 {@link #normalizeLongitude(float)} 一致。
     * @param d 原始经度
     * @return 归一化经度，范围 [-180, 180)
     */
    @VisibleForTesting
    static double normalizeLongitude(double d) {
        return normalizeLongitude((float) d);
    }

    /**
     * 将经度归一化到区间 [-180, 180)。
     * 特殊处理输入值为 -180 的情况，直接返回 -180。
     * 算法说明：
     * - 先取绝对值并按 360 取模得到 [0, 360)；
     * - 若结果 >= 180，则减去 360，使其落入 (-180, 180)；
     * - 根据原始符号恢复正负方向。
     * 该实现与 GIS 常见的经度折返约定兼容。
     * @param d 原始经度
     * @return 归一化经度，范围 [-180, 180)
     */
    public static float normalizeLongitude(float d) {
        if (d == -180.0f)
            return -180.0f;
        else {
            // 记录符号，用于恢复方向
            float sign = Math.signum(d);
            // 对绝对值做 360 的取模，得到 [0, 360) 区间
            float x = Math.abs(d) / 360;
            float x2 = (x - (float) Math.floor(x)) * 360;
            // 折返到 (-180, 180) 区间
            if (x2 >= 180)
                x2 -= 360;
            // 恢复原始符号
            return x2 * sign;
        }
    }

    /**
     * double 版本的经度归一化。
     * 注意：对 {@code Math.floor(x)} 做了 float 强制转换，
     * 以保持与 float 版本在边界与舍入上的一致行为。
     * @param d 原始经度
     * @return 归一化经度，范围 [-180, 180)
     */
    private static double normalizeLongitudeDouble(double d) {
        if (d == -180.0f)
            return -180.0d;
        else {
            // 记录符号，用于恢复方向
            double sign = Math.signum(d);
            // 对绝对值做 360 的取模，得到 [0, 360) 区间
            double x = Math.abs(d) / 360;
            double x2 = (x - (float) Math.floor(x)) * 360;
            // 折返到 (-180, 180) 区间
            if (x2 >= 180)
                x2 -= 360;
            // 恢复原始符号
            return x2 * sign;
        }
    }

}
